/*******************************************************************************
 * Copyright (c) 2008 Bastien Amar.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Bastien Amar - initial API and implementation
 *******************************************************************************/

package fr.irit.etracetool.simpleemf;

import fr.irit.etracetool.etrace.*;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.common.util.EList;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;


public aspect SimpleEmfTracer {
	
	protected ETrace trace;
	
	protected CompositeLink currentCompositeLink;
	
	protected TypesCollection types;
	
	protected Logger logger = Logger.getLogger("fr.irit.etracetool.simpleemf");
	
	public static int inTransfo = 0;
	
	public static boolean LOG = true;
	
	public static boolean fonctionnal = true;
	public static boolean factory = false;
	public static boolean setter = false;
	public static boolean add = true;
	public static boolean remove = false;
	public static boolean procedure = false;
	
	//We don't want to trace tracing methods
	public pointcut NoTracer():!within(fr.irit.etracetool.*) && 
		!within(fr.irit.etracetool.*.*) &&
		!within(fr.irit.etracetool.*.*.*) &&
		!within(fr.irit.etracetool.*.*.*.*);
	
	//Global Transformation call :
	public pointcut TransfoExec(): execution(* transform(..));
	
	//Local Transformation call :
	public pointcut LocalTransfoExec(): execution(* doTransform*(..)) ;

	//First category :
	public pointcut FonctionnalMethod(): execution(EObject+ * (.., EObject+, ..)) && 
			NoTracer() && if (fonctionnal)&&
			if(inTransfo>0);
	
	public pointcut FonctionnalMethodWithList(): execution(Collection<EObject+>+ * (..,EObject+,..)) &&
			NoTracer() && if(fonctionnal)&& 
			if(inTransfo>0);

	//Second category :
	public pointcut FactoryMethod(EObject receveur): (call(EObject+ *(..)) &&
			!FonctionnalMethod() &&	target(receveur)&& if (factory) &&
			if(receveur instanceof EFactory) &&
			NoTracer() && if(inTransfo>0));

	//Third category :
	public pointcut SetterMethod(EObject receveur): 
			call(!EObject+ *(..,EObject+,..))&& if(setter) && 
			if(receveur instanceof EObject)	&&
			NoTracer() && if (inTransfo>0) && target(receveur);


	//Fourth category :
	public pointcut addItem(EList receveur) :
			call(* EList.add*(..)) && if (add) && 
			NoTracer() && if(inTransfo>0) && target(receveur);


	//Fifth category :
	public pointcut removeItem(EList receveur) :
		call(* EList.remove*(..)) && if(remove)&& 
		NoTracer() && if(inTransfo>0) && target(receveur);

	public pointcut procedure(): execution(* * (.., EObject+, ..)) && 
	NoTracer() && if (procedure)&& !FonctionnalMethod() &&
	if(inTransfo >0);

	before(): LocalTransfoExec() {
		if (LOG) {
			logger.log(Level.INFO, "Local transform, trace activated : " + thisJoinPoint.getSignature().toShortString());
		}
		inTransfo++;
	}
	
	after(): LocalTransfoExec() {
		if (LOG) {
			logger.log(Level.INFO, "Local transform terminated, trace deactivated : " + thisJoinPoint.getSignature().toShortString());
		}
		inTransfo--;
	}
	
	before(): TransfoExec() {
		trace = EtraceFactory.eINSTANCE.createETrace();
		types = new TypesCollection(trace);
		trace.setName(thisJoinPoint.getSignature().getName());
		currentCompositeLink = trace;
		if (LOG) { 
			try {
				logger.setUseParentHandlers(false);
				FileHandler fh = new FileHandler("./model/tracelog.xml");
				logger.addHandler(fh);
			}
			catch(IOException e) {
				System.err.println("Problem during the log file creation.");
				e.printStackTrace();
			}
		}
		else logger.setLevel(Level.OFF);
		logger.log(Level.INFO, "Trace created.");
	}
	
	after(): TransfoExec() {
		addContainer(trace);
		Persistent.SaveTrace(trace, "./model/trace.xmi");
		logger.log(Level.INFO,"Trace saved.");
		fr.irit.etracetool.etrace.util.EtraceToDot ed = new fr.irit.etracetool.etrace.util.EtraceToDot();
		File dotFile = new File("./model/trace.dot");
		try {
			FileWriter fwDotFile = new FileWriter(dotFile);
			fwDotFile.write(ed.generate(this.trace));
			fwDotFile.flush();
			fwDotFile.close();
			logger.log(Level.INFO,"Dot file saved.");
		}
		catch (IOException e) {
			logger.log(Level.SEVERE, "Problem during the creation of .dot file in SimpleEmfTracer, exiting.");
			System.exit(1);
		}
	}

	Object around(): FonctionnalMethod() {
		logger.log(Level.INFO, "Fonctionnal method found");
		CompositeLink current = this.currentCompositeLink;
		CompositeLink children = EtraceFactory.eINSTANCE.createCompositeLink();
		
		LinkType lt = EtraceFactory.eINSTANCE.createLinkType();
		lt.setName(thisJoinPoint.getSignature().getName());
		lt.setDescription(this.types.firstCategory.getDescription());
		lt.setExample(this.types.firstCategory.getExample());
		lt.setPurpose(this.types.firstCategory.getPurpose());
		lt.setUses(this.types.firstCategory.getUses());
		this.types.firstCategory.getSubType().add(lt);
		trace.getTypeList().add(lt);
		
		children.setType(lt);
		currentCompositeLink=children;
		Object o = proceed();
		
		if (currentCompositeLink.getChildren().size()!=0) {
			current.getChildren().add(currentCompositeLink);
		}
		currentCompositeLink=current;
		for (int i=0; i<thisJoinPoint.getArgs().length; i++) {
			if (thisJoinPoint.getArgs()[i] instanceof EObject) {
				Link l = EtraceFactory.eINSTANCE.createLink();
				l.setSource((EObject) thisJoinPoint.getArgs()[i]);
				
				LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
				ltype.setName(thisJoinPoint.getSignature().getName());
				ltype.setDescription(this.types.firstCategory.getDescription());
				ltype.setExample(this.types.firstCategory.getExample());
				ltype.setPurpose(this.types.firstCategory.getPurpose());
				ltype.setUses(this.types.firstCategory.getUses());
				this.types.firstCategory.getSubType().add(ltype);
				trace.getTypeList().add(ltype);
				
				l.setTarget((EObject) o);
				l.setType(ltype);
				currentCompositeLink.getChildren().add(l);
			}
		}
		return o;
	}
	
	Object around(): FonctionnalMethodWithList() {
		logger.log(Level.INFO, "Fonctionnal method found, with multiple parameters");
		CompositeLink current = this.currentCompositeLink;
		CompositeLink children = EtraceFactory.eINSTANCE.createCompositeLink();
		
		LinkType lt = EtraceFactory.eINSTANCE.createLinkType();
		lt.setName(thisJoinPoint.getSignature().getName());
		lt.setDescription(this.types.firstCategory.getDescription());
		lt.setExample(this.types.firstCategory.getExample());
		lt.setPurpose(this.types.firstCategory.getPurpose());
		lt.setUses(this.types.firstCategory.getUses());
		this.types.firstCategory.getSubType().add(lt);
		trace.getTypeList().add(lt);
		
		children.setType(lt);
		currentCompositeLink=children;

		Object o = proceed();
		
		if (currentCompositeLink.getChildren().size()!=0) {
			current.getChildren().add(currentCompositeLink);
		}
		currentCompositeLink=current;
		for (int i=0; i< thisJoinPoint.getArgs().length; i++) {
			if (thisJoinPoint.getArgs()[i] instanceof EObject) {
				//On lie les arguments de la fonction à tous les objets de la liste renvoyée.
				Collection<EObject> col = (Collection<EObject>) o;
				for (EObject eo: col) {
					Link l = EtraceFactory.eINSTANCE.createLink();
					l.setSource((EObject) thisJoinPoint.getArgs()[i]);
					
					LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
					ltype.setName(thisJoinPoint.getSignature().getName());
					ltype.setDescription(this.types.firstCategory.getDescription());
					ltype.setExample(this.types.firstCategory.getExample());
					ltype.setPurpose(this.types.firstCategory.getPurpose());
					ltype.setUses(this.types.firstCategory.getUses());
					this.types.firstCategory.getSubType().add(ltype);
					trace.getTypeList().add(ltype);
					
					l.setTarget(eo);
					l.setType(ltype);
					currentCompositeLink.getChildren().add(l);
				}
			}
		}
		return o;
	}
	
	Object around(EObject receveur) : FactoryMethod(receveur) {
		logger.log(Level.INFO, "Factory method found");
		Link l = EtraceFactory.eINSTANCE.createLink();
		
		LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
		ltype.setName(thisJoinPoint.getSignature().getName());
		ltype.setDescription(this.types.secondCategory.getDescription());
		ltype.setExample(this.types.secondCategory.getExample());
		ltype.setPurpose(this.types.secondCategory.getPurpose());
		ltype.setUses(this.types.secondCategory.getUses());
		this.types.secondCategory.getSubType().add(ltype);
		trace.getTypeList().add(ltype);

		Object o = proceed(receveur);
		
		trace.getExternalElement().add((EObject) receveur);		
		l.setSource((EFactory) receveur);
		l.setTarget((EObject) o);
		l.setType(ltype);
		currentCompositeLink.getChildren().add(l);
		return o;
	}
	
	Object around(EObject receveur) : SetterMethod(receveur) {
		logger.log(Level.INFO, "Setter Method found");

		Object o = proceed(receveur);
		
		Object[] args = thisJoinPoint.getArgs();
		for (int i=0; i<args.length; i++) {
			if (args[i] instanceof EObject) {
				Link l = EtraceFactory.eINSTANCE.createLink();
				
				LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
				ltype.setName(thisJoinPoint.getSignature().getName());
				ltype.setDescription(this.types.thirdCategory.getDescription());
				ltype.setExample(this.types.thirdCategory.getExample());
				ltype.setPurpose(this.types.thirdCategory.getPurpose());
				ltype.setUses(this.types.thirdCategory.getUses());
				this.types.thirdCategory.getSubType().add(ltype);
				trace.getTypeList().add(ltype);
				
				l.setSource((EObject)thisJoinPoint.getArgs()[i]);
				l.setTarget(receveur);
				l.setType(ltype);
				currentCompositeLink.getChildren().add(l);
			}
		}		
		return o;
	}
	
	Object around(EList receveur) : addItem(receveur) {
		logger.log(Level.INFO, "Add* method found");
		Object e = proceed(receveur);
		if (thisJoinPoint.getArgs()[0] instanceof EObject) {
				Link l = EtraceFactory.eINSTANCE.createLink();
				
				LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
				ltype.setName(thisJoinPoint.getSignature().getName());
				ltype.setDescription(this.types.fourthCategory.getDescription());
				ltype.setExample(this.types.fourthCategory.getExample());
				ltype.setPurpose(this.types.fourthCategory.getPurpose());
				ltype.setUses(this.types.fourthCategory.getUses());
				this.types.fourthCategory.getSubType().add(ltype);
				trace.getTypeList().add(ltype);
				
				l.setSource((EObject) thisJoinPoint.getArgs()[0]);
				l.setTarget(((EObject) thisJoinPoint.getArgs()[0]).eContainer());
				l.setType(ltype);
				currentCompositeLink.getChildren().add(l);			
		}
		return e;
	}
	

	
	Object around(EList receveur) : removeItem(receveur) {
		if (thisJoinPoint.getArgs()[0] instanceof Collection) {
			logger.log(Level.INFO, "remove(Collection) method found");
			Collection args = (Collection) thisJoinPoint.getArgs()[0];
			for (Object o : args){
				if (o instanceof EObject) {
					trace.getDeletedElement().add((EObject) o);
					
					Link l = EtraceFactory.eINSTANCE.createLink();
					
					LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
					ltype.setName(thisJoinPoint.getSignature().getName());
					ltype.setDescription(this.types.fifthCategory.getDescription());
					ltype.setExample(this.types.fifthCategory.getExample());
					ltype.setPurpose(this.types.fifthCategory.getPurpose());
					ltype.setUses(this.types.fifthCategory.getUses());
					this.types.fifthCategory.getSubType().add(ltype);
					trace.getTypeList().add(ltype);
					
					l.setSource((EObject) o);
					//Pas de target ...
					l.setType(ltype);
					currentCompositeLink.getChildren().add(l);			
				}
			}	
		}
		else if (thisJoinPoint.getArgs()[0] instanceof EObject) {
			trace.getDeletedElement().add((EObject) thisJoinPoint.getArgs()[0]);
			logger.log(Level.INFO, "remove(EObject) method found");
			Link l = EtraceFactory.eINSTANCE.createLink();
			
			LinkType ltype = EtraceFactory.eINSTANCE.createLinkType();
			ltype.setName(thisJoinPoint.getSignature().getName());
			ltype.setDescription(this.types.fifthCategory.getDescription());
			ltype.setExample(this.types.fifthCategory.getExample());
			ltype.setPurpose(this.types.fifthCategory.getPurpose());
			ltype.setUses(this.types.fifthCategory.getUses());
			this.types.fifthCategory.getSubType().add(ltype);
			trace.getTypeList().add(ltype);
			
			l.setSource((EObject) thisJoinPoint.getArgs()[0]);
			//Pas de target...
			l.setType(ltype);
			currentCompositeLink.getChildren().add(l);				
		}
		Object o = proceed(receveur);
		return o;
	}
	
	private void addContainer(AbstractLink trace) {
		if ((trace.getSource() != null) && (trace.getSource().eResource() == null)) {
			this.trace.getExternalElement().add(trace.getSource());
			logger.log(Level.WARNING, "A source EObject isn't contained in a Resource, so it saved in the generic trace container :"+trace.getSource().toString());
		}
		if ((trace.getTarget() != null) && (trace.getTarget().eResource() == null)) {
			this.trace.getExternalElement().add(trace.getTarget());
			logger.log(Level.WARNING, "A target EObject isn't contained in a Resource, so it saved in the generic trace container : "+trace.getTarget().toString());
		}
		if (trace instanceof CompositeLink) {
			for (AbstractLink cl : ((CompositeLink)trace).getChildren()) {
				addContainer(cl);
			}
		}
	}
	
	Object around(): procedure() {
		logger.log(Level.INFO, "procedure method found : " + thisJoinPoint.getSignature().getName());
		CompositeLink current = this.currentCompositeLink;
		CompositeLink children = EtraceFactory.eINSTANCE.createCompositeLink();

		LinkType lt = EtraceFactory.eINSTANCE.createLinkType();
		lt.setName(thisJoinPoint.getSignature().getName());
		lt.setDescription(this.types.firstCategory.getDescription());
		lt.setExample(this.types.firstCategory.getExample());
		lt.setPurpose(this.types.firstCategory.getPurpose());
		lt.setUses(this.types.firstCategory.getUses());
		this.types.firstCategory.getSubType().add(lt);
		trace.getTypeList().add(lt);
	
		children.setType(lt);
		currentCompositeLink=children;
		Object o = proceed();
		
		if (currentCompositeLink.getChildren().size()!=0) {
			current.getChildren().add(currentCompositeLink);
		}
		currentCompositeLink=current;
		return o;
	}

}
